Fantomas is trying to format the input multiple times due to the detection of multiple defines
As explained in Formatting Conditional Compilation Directives, Fantomas will try to format the input multiple times if it detects multiple defines.
The amount of conditional directives should be exactly the same in each pass. This is a requirement in order for Fantomas to merge the results into one.
Unfortunately, this is not always the case and an exception will be thrown if the bookkeeping doesn't add up.
As a case-study, we will look at issue #2844 and see how we troubleshoot these types of issues.
System.FormatException: Fantomas is trying to format the input multiple times due to the detection of multiple defines.
There is a problem with merging all the code back together.
[] has 7 fragments
[IOS] has 9 fragments
Isolate each define combination
The first step is to isolate each define combination in a unit test. Doing this will simplify the debugging process.
The formatSourceStringWithDefines
can be used to only format the input with a specific set of defines.
[<Test>]
let ``good unit test name, no defines`` () =
formatSourceStringWithDefines
[]
"""
program.SyncAction
(
#if IOS
// iOS animates by default layout changes, we don't want that
fun () -> v
#else
fn
#endif
)
"""
config
|> prepend newline
|> should
equal
"""
program.SyncAction(
#if IOS
#else
fn
#endif
)
"""
Notice that our result code should reflect only the active code branches.
IOS
is not present and so no code is expected between #if IOS
and #else
.
If we do this for each combination, we can narrow the problem down to find the troublesome combination.
[<Test>]
let ``good unit test name, IOS`` () =
formatSourceStringWithDefines
[ "IOS" ]
"""
program.SyncAction
(
#if IOS
// iOS animates by default layout changes, we don't want that
fun () -> v
#else
fn
#endif
)
"""
config
|> prepend newline
|> fun r ->
printfn "%s" r
r
|> should
equal
"""
program.SyncAction
(
#if IOS
// iOS animates by default layout changes, we don't want that
fun () -> v
#else
#endif
)
"""
Bringing it all together
If each combination is fixed, we can now try to format the input with all the defines.
Notice that we use formatSourceString
instead of formatSourceStringWithDefines
here.
[<Test>]
let ``good unit test name, 2844`` () =
formatSourceString
false
"""
program.SyncAction
(
#if IOS
// iOS animates by default layout changes, we don't want that
fun () -> v
#else
fn
#endif
)
"""
config
|> prepend newline
|> should
equal
"""
program.SyncAction
(
#if IOS
// iOS animates by default layout changes, we don't want that
fun () -> v
#else
fn
#endif
)
"""
Unit test naming conventions
When dealing with multiple defines, it is important to name the unit tests in a way that makes it easy to understand what is going on.
Use the following naming convention suffix:
, no defines
for the[]
case, defineA defineB
for the[ "defineA"; "defineB" ]
case, issue-number
for the full test.
type FormatException = inherit SystemException new: unit -> unit + 2 overloads
<summary>The exception that is thrown when the format of an argument is invalid, or when a composite format string is not well formed.</summary>
--------------------
System.FormatException() : System.FormatException
System.FormatException(message: string) : System.FormatException
System.FormatException(message: string, innerException: exn) : System.FormatException